package pkmn.battle;

/*
 * Agradecimento especial a Marcus, vulgo Bubu, por ter me dado "A dica" de como
 * implementar os ataques de 2 turnos.
 */

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Scanner;
import pkmn.Pokemon;
import pkmn.Trainer;
import pkmn.exception.DeathException;
import pkmn.move.Charge;
import pkmn.status.BattleStatus;
import pkmn.status.BattleStatusEnum;
import pkmn.status.StatusEnum;
import pkmn.util.Util;

public class TrainerBattle {

    private Trainer trainer1;
    private Trainer trainer2;
    private BattlePokemon battlePokemon1;
    private BattlePokemon battlePokemon2;

    public TrainerBattle(Trainer trainer1, Trainer trainer2) {
        this.trainer1 = trainer1;
        this.trainer2 = trainer2;

        this.battlePokemon1 = new BattlePokemon(trainer1.getPokemon(0));
        this.battlePokemon2 = new BattlePokemon(trainer2.getPokemon(0));
    }

    /**
     * Called before attack
     */
    private void beforeAttack(BattlePokemon battlePokemon, boolean first) throws DeathException {
        //Check status
        //Paralized
        if (battlePokemon.getPokemon().getStatus().equals(StatusEnum.paralyzis)) {
            if (battlePokemon.getPokemon().getStatus().getTurns() == 0) {
                battlePokemon.getPokemon().removeStatus();
                Util.printAndWait(battlePokemon + " paralyzed no more.");
                battlePokemon.canAttack(true);
            } else if (Util.probability(30.0)) {
                battlePokemon.getPokemon().getStatus().decTurns();
                Util.printAndWait(battlePokemon + " is fully paralyzed!");
                battlePokemon.canAttack(false);
                return;
            }
        }
        //Sleeping
        if (battlePokemon.getPokemon().getStatus().equals(StatusEnum.sleep)) {
            if (battlePokemon.getPokemon().getStatus().getTurns() == 0) {
                battlePokemon.getPokemon().removeStatus();
                Util.printAndWait(battlePokemon + " woke up!");
                battlePokemon.canAttack(true);
            } else {
                battlePokemon.getPokemon().getStatus().decTurns();
                Util.printAndWait(battlePokemon + " is fast asleep.");
                battlePokemon.canAttack(false);
                return;
            }
        }

        //Others status
        List<BattleStatus> listBattleStatus = battlePokemon.getBattleStatus();
        //Flinch
        for (BattleStatus bs : new ArrayList<BattleStatus>(listBattleStatus)) {
            if (!first && bs.equals(BattleStatusEnum.flinch)) {
                Util.printAndWait(battlePokemon + " flinched!");
                battlePokemon.canAttack(false);
                battlePokemon.removeBattleStatus(BattleStatusEnum.flinch);
                return;
            }
        }
        //Confusion
        for (BattleStatus bs : new ArrayList<BattleStatus>(listBattleStatus)) {
            if (bs.equals(BattleStatusEnum.confusion)) {
                if (bs.getTurns() == 0) {
                    listBattleStatus.remove(bs);
                    Util.printAndWait(battlePokemon + " snapped out of confusion.");
                    battlePokemon.canAttack(true);
                } else {
                    bs.decTurns();
                    Util.printAndWait(battlePokemon + " is confused!");
                    if (Util.probability(50)) {
                        Util.printAndWait("It hurt itself in its confusion!");
                        int damage = battlePokemon.getPokemon().getMaxLife() / 16;
                        battlePokemon.getPokemon().receiveDamage(damage);
                        battlePokemon.canAttack(false);
                        return;
                    }
                    battlePokemon.canAttack(true);
                }
            }
        }
        //Charge
        if (battlePokemon.haveBattleStatus(BattleStatusEnum.charge)) {
            battlePokemon.canAttack(false);
            return;
        } else {
            battlePokemon.canAttack(true);
        }

        //Recharge
        if (battlePokemon.haveBattleStatus(BattleStatusEnum.recharge)) {
            Util.printAndWait(battlePokemon + " must recharge!");
            battlePokemon.canAttack(false);
            battlePokemon.removeBattleStatus(BattleStatusEnum.recharge);
            return;
        } else {
            battlePokemon.canAttack(true);
        }
    }

    /**
     * Called after attack
     */
    private void afterAttack(BattlePokemon battlePokemon) {
    }

    /**
     * Called before turn
     */
    private void beforeTurn() throws DeathException {
    }

    /**
     * Called after turn
     * @throws DeathException
     */
    private void afterTurn() throws DeathException {
        this.afterTurn(this.battlePokemon1);
        this.afterTurn(this.battlePokemon2);
        this.afterTurn(this.battlePokemon1, this.battlePokemon2);
        this.afterTurn(this.battlePokemon2, this.battlePokemon1);
    }

    private void afterTurn(BattlePokemon battlePokemon) throws DeathException {

        //Poisoned
        if (battlePokemon.getPokemon().getStatus().equals(StatusEnum.poison)) {
            Util.printAndWait(battlePokemon + " is hurt by poison!");
            battlePokemon.getPokemon().receiveDamage(battlePokemon.getPokemon().getMaxLife() / 16);
        }
        //Burned
        if (battlePokemon.getPokemon().getStatus().equals(StatusEnum.burn)) {
            battlePokemon.setAttackLevel(0.5);
            Util.printAndWait(battlePokemon + " is hurt by burn!");
            battlePokemon.getPokemon().receiveDamage(battlePokemon.getPokemon().getMaxLife() / 16);
        }
        //others status
        //wrap
        List<BattleStatus> listBattleStatus = battlePokemon.getBattleStatus();
        for (BattleStatus bs : new ArrayList<BattleStatus>(listBattleStatus)) {
            if (bs.equals(BattleStatusEnum.wrap)) {
                if (bs.getTurns() == 0) {
                    listBattleStatus.remove(bs);
                    Util.printAndWait(battlePokemon + " was freed from wrap!");
                } else {
                    bs.decTurns();
                    Util.printAndWait(battlePokemon + " is hurt by wrap!");
                    int damage = battlePokemon.getPokemon().getMaxLife() / 16;
                    battlePokemon.getPokemon().receiveDamage(damage);
                }
            }
        }

        battlePokemon.canAttack(true);
    }

    private void afterTurn(BattlePokemon battlePokemon1, BattlePokemon battlePokemon2) throws DeathException {
        //BattleStatusEnum
        List<BattleStatus> listBattleStatus = battlePokemon1.getBattleStatus();
        for (BattleStatus bs : listBattleStatus) {
            if (bs.equals(BattleStatusEnum.seed)) {
                int damage = battlePokemon1.getPokemon().getMaxLife() / 16;
                Util.printAndWait(battlePokemon1 + " health is sapped by Leech Seed!");
                battlePokemon1.getPokemon().receiveDamage(damage);
                battlePokemon2.getPokemon().addLife(damage);
                break;
            }
        }

        battlePokemon1.canAttack(true);
        battlePokemon2.canAttack(true);
    }

    private void beforeFight() {
    }

    private void afterFight() {
        int exp = this.battlePokemon2.getPokemon().getExpBattle();
        this.battlePokemon1.getPokemon().addExp(exp);
        System.out.println(this.battlePokemon1 + " gained " + exp + " EXP points.");
    }

    private int battleMenu1() {
        Scanner scanner = new Scanner(System.in);
        System.out.println(trainer1.getName() + ": " + trainer1.getPokemonList());
        System.out.println(trainer2.getName() + ": " + trainer2.getPokemonList());
        System.out.println(this.battlePokemon1.getVisualLife());
        System.out.println(this.battlePokemon2.getVisualLife());
        System.out.println("Choose a action: ");
        System.out.println("1- Attack");
        System.out.println("2- Pokémon");
        System.out.println("3- Bag");
        System.out.println("4- Run");
        return scanner.nextInt();
    }

    private int battleMenu2() {
        Scanner scanner = new Scanner(System.in);
        //Menu of battle
        System.out.println(this.battlePokemon1.getMovesMenu());
        return scanner.nextInt() - 1;
    }

    public void fight() {
        Util.printAndWait(trainer2.getName() + " wants to fight!");

        Util.printAndWait(trainer2.getName() + " send your " + battlePokemon2 + ".");
        Util.printAndWait("Go, " + battlePokemon1 + "!");

        Random random = new Random();

        int menu;
        int index1 = 0;
        int index2 = 0;
        while (true) {
            try {
                beforeTurn();
                menu = battleMenu1();
                if (menu == 1) {
                    if (!this.battlePokemon1.haveBattleStatus(BattleStatusEnum.recharge)
                            && !this.battlePokemon1.haveBattleStatus(BattleStatusEnum.charge)) {
                        index1 = battleMenu2();
                    }
                } else if (menu == 2) {
                    this.choosePokemonMenu();
                    this.battlePokemon1.canAttack(false);
                } else if (menu == 3) {
                    Util.printAndWait("Item not implemented!");
                    continue;
                } else if (menu == 4) {
                    System.out.println("You run of battle.");
                    System.exit(0);
                }

                int numberAttacks = 0;
                while (this.battlePokemon2.getPokemon().getMove()[numberAttacks] != null) {
                    numberAttacks++;
                    if (numberAttacks > 3) {
                        break;
                    }
                }

                if (!this.battlePokemon2.haveBattleStatus(BattleStatusEnum.recharge)
                        && !this.battlePokemon2.haveBattleStatus(BattleStatusEnum.charge)) {
                    index2 = random.nextInt(numberAttacks);
                }

                //check if move need charge
                if (this.battlePokemon1.haveBattleStatus(BattleStatusEnum.charge)) {
                    this.battlePokemon1.removeBattleStatus(BattleStatusEnum.charge);
                } else {
                    if (this.battlePokemon1.getPokemon().getMove(index1).getIndirectAction() instanceof Charge) {
                        ((Charge)(this.battlePokemon1.getPokemon().getMove(index1).getIndirectAction())).executeBefore(battlePokemon1);
                        this.battlePokemon1.setBattleStatus(BattleStatusEnum.charge);
                    }
                }
                if (this.battlePokemon2.haveBattleStatus(BattleStatusEnum.charge)) {
                    this.battlePokemon2.removeBattleStatus(BattleStatusEnum.charge);
                } else {
                    if (this.battlePokemon2.getPokemon().getMove(index2).getIndirectAction() instanceof Charge) {
                        ((Charge)(this.battlePokemon2.getPokemon().getMove(index1).getIndirectAction())).executeBefore(battlePokemon2);
                        this.battlePokemon2.setBattleStatus(BattleStatusEnum.charge);
                    }
                }

                int priority1 = this.battlePokemon1.getPokemon().getMove(index1).getPriority();
                int priority2 = this.battlePokemon2.getPokemon().getMove(index2).getPriority();

                boolean pkmn1first;
                if (priority1 > priority2) {
                    pkmn1first = true;
                } else if (priority1 < priority2) {
                    pkmn1first = false;
                } else {
                    if (this.battlePokemon1.getSpeed() > this.battlePokemon2.getSpeed()) {
                        pkmn1first = true;
                    } else {
                        pkmn1first = false;
                    }
                }

                if (pkmn1first) {
                    this.beforeAttack(this.battlePokemon1, true);
                    if (this.battlePokemon1.canAttack()) {
                        this.battlePokemon1.attack(index1, this.battlePokemon2);
                    }
                    this.afterAttack(this.battlePokemon2);
                    //
                    this.beforeAttack(this.battlePokemon2, false);
                    if (this.battlePokemon2.canAttack()) {
                        this.battlePokemon2.attack(index2,
                                this.battlePokemon1);
                    }
                    this.afterAttack(this.battlePokemon2);

                } else {

                    this.beforeAttack(this.battlePokemon2, true);
                    if (this.battlePokemon2.canAttack()) {
                        this.battlePokemon2.attack(index2,
                                this.battlePokemon1);
                    }
                    this.afterAttack(this.battlePokemon2);
                    //
                    this.beforeAttack(this.battlePokemon1, false);
                    if (this.battlePokemon1.canAttack()) {
                        this.battlePokemon1.attack(index1, this.battlePokemon2);
                    }
                    this.afterAttack(this.battlePokemon1);
                }

                afterTurn();
            } catch (DeathException de) {
                Util.printAndWait(de.getMessage());

                if (!trainer1.haveAvaliablePokemon()) {
                    System.out.println("You lose.");
                    break;
                }
                if (!trainer2.haveAvaliablePokemon()) {
                    System.out.println("You won the battle!");
                    break;
                }

                if (battlePokemon1.fainted()) {
                    this.choosePokemonMenu();
                }

                if (battlePokemon2.fainted()) {
                    this.choosePokemon2();
                }
            }
        }
        this.afterFight();
    }

    private void choosePokemon2() {
        Pokemon p;
        for (int i = 0; i < 6 && this.trainer1.getPokemon(i) != null; i++) {
            p = this.trainer2.getPokemon(i);
            if (p.fainted()) {
                continue;
            }
            this.battlePokemon2.setPokemon(p);
            Util.printAndWait(this.trainer2.getName() + " send your " + this.battlePokemon2 + ".");
            break;
        }
    }

    private void choosePokemonMenu() {
        Scanner scanner = new Scanner(System.in);
        while (true) {
            System.out.println("Choose a Pokémon:");
            for (int i = 0; i < 6 && this.trainer1.getPokemon(i) != null; i++) {
                System.out.println((i + 1) + "- " + this.trainer1.getPokemon(i).getName()
                        + (this.trainer1.getPokemon(i).getStatus().equals(StatusEnum.none) ? "" : " (" + this.trainer1.getPokemon(i).getStatus() + ")"));
            }
            int menu = scanner.nextInt() - 1;
            Pokemon p = this.trainer1.getPokemon(menu);
            if (p == null || p.fainted()) {
                System.out.println("Pokémon unavaliable.");
                continue;
            }
            if (p.getName().equals(battlePokemon1.toString())) {
                System.out.println("This pokémon already on battle!");
                continue;
            }
            Util.printAndWait("Ok " + battlePokemon1 + ", go back!");
            battlePokemon1.setPokemon(this.trainer1.getPokemon(menu));
            Util.printAndWait("Go, " + battlePokemon1 + "!");
            break;
        }
    }
}
